#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#include "msg_buf.h"
#include "msg_task.h"
#include "uds_tp.h"
#include "msg_port.h"


// 因手头上暂时不方便使用硬件 can 设备调试工具
// 在这里实现一个简易的服务端程序（同时只支持一个客户端连接）
// 此时我们利用网络调试助手模拟 can 设备，向该服务器发送 10 个字节的数据，前两个字节模拟 can id，后面 8 个字节模拟数据
// 这里没有处理粘包问题，在利用网络调试助手发送数据时发送间隔不能太短

#define SERVER_PORT         3389
#define LISTEN_MAX          1

#define RX_MAX              128

int clientSockfd = -1;

void* pthread_handle_client(void *arg)
{
    int ret = -1;
    int serverSockfd = -1;
    
    // 1. 创建套接字 - socket
    serverSockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(serverSockfd < 0)
    {
        printf("Server socket error.\n");
        return NULL;    
    }

    // SO_REUSEADDR 允许启动一个监听服务器并捆绑其众所周知端口，即使以前建立的将此端口用做他们的本地端口的连接仍存在。
    // 这通常是重启监听服务器时出现，若不设置此选项，则 bind 时将出错。
    int on = 1;
    setsockopt(serverSockfd, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));

    // 2. 绑定套接字 - bind
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;                        // 设置 tcp 协议族
    serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);         // 设置 ip 地址
    serverAddr.sin_port = htons(SERVER_PORT);               // 设置 port 端口号
    ret = bind(serverSockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    if(ret < 0)
    {
        printf("Server bind error\n");
        return NULL;
    }

    // 3. 开启监听 - listen
    ret = listen(serverSockfd, LISTEN_MAX);
    if(ret < 0)
    {
        printf("Server listen error\n");
        return NULL;
    }

    while(1)
    {
        // 4. 等待客户端连接 - accept
        struct sockaddr_in clientAddr;
        socklen_t clientAddrLen = sizeof(clientAddr);
        // int clientSockfd = -1;
        clientSockfd = accept(serverSockfd,  (struct sockaddr *)&clientAddr, &clientAddrLen);
        if(clientSockfd < 0)
        {
            printf("Accept client error\n");
            break;
        }

        // 客户端已成功连接，接下来在 while(1) 中一直处理来自客户端的数据
        while(1)
        {
            uint8_t rxbuf[RX_MAX] = {0};
            int recvLen = recv(clientSockfd, rxbuf, RX_MAX, 0);
            // 若接收到的数据长度为 0，则表明客户端断开了连接
            if(recvLen <= 0)
                break;
            recvLen = (recvLen < RX_MAX) ? recvLen : RX_MAX;
            
            // 将数据发送到接收循环缓冲区中
            Msg_S msg = {0};
            msg.id = rxbuf[0] << 8 | rxbuf[1];
            memcpy(msg.data, &rxbuf[2], 8);
            // PutToRxBuf(&msg);
            ll_read(&msg);
        }
    }
}

uint8_t uds_rxbuf[1024];
int test_rxComplCallback(uint8_t* rxbuf, uint16_t len)
{
    for(int i = 0; i < len; i++)
        printf("0x%x ", rxbuf[i]);
    printf("\n");
}

int main(void)
{
    int ret = -1;


    // 创建客户端处理线程
    pthread_t client_thrd = 0;
    ret = pthread_create(&client_thrd, NULL, pthread_handle_client, NULL);
    if(ret != 0)
    {
        printf("pthread handle_client creat error\n");
        return -1;
    }
    // pthread_detach 作用：线程主动与主控线程断开关系。线程结束后（不会产生僵尸线程），其退出状态不由其他线程获取，而直接自己自动释放（自己清理掉PCB的残留资源）
    pthread_detach(client_thrd);

    UdsTpRecvReg(uds_rxbuf, 1024, (pRxcomplFunc)test_rxComplCallback);

    while(1)
    {
        // 1ms
        Msg1msTask();
        UdsTp1msTask();


        int UdsTpSend(uint8_t* txbuf, uint16_t txlen, pTxcomplFunc txComplCallback);
        static uint32_t cnt = 0;
        if(cnt < 1000)
            cnt++;
        else
        {
            cnt = 0;
            static uint8_t TxBuf[100] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
            UdsTpSend(TxBuf, 5, NULL);
        }
        usleep(1000);
    }

    return 0;
}
